﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text;
using System.Windows.Forms;
using fastJSON;
using gtasks.Utils;
using pub.GUI;
using pub.NET20Fix;
using pub.Settings;
using pub.Types;
using todo;

namespace gtasks.Auth
{
    public class AuthManager
    {
        //data1 = refrsh_token
        //data2 = access_token
        //data3 = expire_time

        public static AuthManager Instance = new AuthManager();

        private ISettings _settings;

        private string _refreshToken;
        private string _token;
        private DateTime _expireTime;

        private Action<WebRequest> _callback;
        private Func<WebRequest> _requestCreator;

        private volatile int _callbackRetryCount;

        private bool _aborting;
        private WebRequest _request;

        public event Action<Exception> OnException;

        public AuthManager()
        {
            _settings = new IniSettings();
            try
            {
                var ts = _settings["gtask"]["data1"].StringValue;
                _refreshToken = CryptUtils.RijndaelDecrypt(ts, "gtask");

                ts = _settings["gtask"]["data2"].StringValue;
                _token = CryptUtils.RijndaelDecrypt(ts, "gtask");

                ts = _settings["gtask"]["data3"].StringValue;
                ts = CryptUtils.RijndaelDecrypt(ts, "gtask");
                _expireTime = DateTime.ParseExact(ts, "yyyyMMddHHmmss", CultureInfo.InvariantCulture);
            }
            catch (Exception e)
            {
                Debug.Print(e.Message);
            }
        }

        public void RequestToken(Action<string> callback)
        {
            _aborting = false;
            if (string.IsNullOrEmpty(_token) || DateTime.Now > _expireTime.AddMinutes(-1))
            {
                AcquireToken(() => callback(_token));
            }
            else
            {
                callback(_token);
            }
        }

        public void AuthExecute(Func<WebRequest> requestCreator, Action<WebRequest> callback)
        {
            _aborting = false;

            _requestCreator = requestCreator;
            _callback = callback;
            _callbackRetryCount = 0;

            if (string.IsNullOrEmpty(_token) || DateTime.Now > _expireTime.AddMinutes(-1))
            {
                AcquireToken(OnTokenReady);
            }
            else
            {
                OnTokenReady();
            }
        }

        private void AcquireToken(Action callback)
        {
            if (string.IsNullOrEmpty(_refreshToken))
            {
                Dispatcher.Current.Invoke(() => {
                    var frm = new AuthForm();
                    frm.ShowDialog(FormUtils.GetLastForm());
                    var code = frm.Code;
                    if (!string.IsNullOrEmpty(code))
                    {
                        AcquireNewToken(code, callback);
                    }
                    else
                    {
                        AsyncUtils.FireOnException(OnException, new AbortException());
                    }
                });
            }
            else
            {
                RenewToken(callback);
            }
        }

        private void OnTokenReady()
        {
            const int MAX_RETRY_COUNT = 2;
            if (_aborting)
            {
                return;
            }

            if (++_callbackRetryCount > MAX_RETRY_COUNT)
            {
                _callbackRetryCount = 0;
                Dispatcher.Current.Invoke(() => {
                    var msg = "Authorization failed";
#if zh
                    msg = "授权失败";
#endif
                    MessageBox.Show(FormUtils.GetLastForm(), msg);
                });
                _refreshToken = null;
                AcquireToken(OnTokenReady);
            }

            var req = _requestCreator();
            req.Headers.Add("Authorization: Bearer " + _token);
            try
            {
                _callback(req);
            }
            catch (WebException e)
            {
                var httpRsp = e.Response as HttpWebResponse;
                if (httpRsp != null && httpRsp.StatusCode == HttpStatusCode.Unauthorized)
                {
                    RenewToken(OnTokenReady);
                }
                else
                {
                    AsyncUtils.FireOnException(OnException, e);
                }
            }
        }

        private void AcquireNewToken(string code, Action callback)
        {
            Debug.Print("@@@ requesting new token @" + DateTime.Now);
            var now = DateTime.Now;

            var url = " https://accounts.google.com/o/oauth2/token";
            var req = WebRequest.Create(url);
            SetCurrentRequest(req);

            req.Method = "POST";
            req.ContentType = "application/x-www-form-urlencoded";
            var qs = "code=" + code + "&" +
                    "client_id=3088305420.apps.googleusercontent.com&" +
                            "client_secret=NBY47cSr12KuRG9tYxWqIq1w&" +
                                    "redirect_uri=urn:ietf:wg:oauth:2.0:oob&" +
                                            "grant_type=authorization_code";
            var bts = Encoding.ASCII.GetBytes(qs);
            req.ContentLength = bts.Length;

            AsyncCallback GetResponseCallback = ar => {
                HttpWebResponse rsp;
                try
                {
                    rsp = req.EndGetResponse(ar) as HttpWebResponse;
                }
                catch (Exception e)
                {
                    AsyncUtils.FireOnException(OnException, e);
                    return;
                }
                using (rsp)
                {
                    TextReader reader = new StreamReader(rsp.GetResponseStream());
                    var text = reader.ReadToEnd();
                    var result = JSON.Instance.ToObject<Dictionary<string, object>>(text);
                    _token = (string) result["access_token"];
                    _refreshToken = (string) result["refresh_token"];
                    var expiresIn = int.Parse("" + result["expires_in"]);
                    _expireTime = now.AddSeconds(expiresIn);
                }
                _settings["gtask"]["data1"].StringValue = CryptUtils.RijndaelEncrypt(_refreshToken,
                        "gtask");
                _settings["gtask"]["data2"].StringValue = CryptUtils.RijndaelEncrypt(_token, "gtask");
                var ts = _expireTime.ToString("yyyyMMddHHmmss");
                _settings["gtask"]["data3"].StringValue = CryptUtils.RijndaelEncrypt(ts, "gtask");
                callback();
            };

            AsyncCallback GetRequestCallback = ar => {
                using (var stream = req.EndGetRequestStream(ar))
                {
                    stream.Write(bts, 0, bts.Length);
                }
                req.BeginGetResponse(GetResponseCallback, req);
            };

            req.BeginGetRequestStream(GetRequestCallback, req);
        }

        private void RenewToken(Action callback)
        {
            Debug.Print("@@@ renewing token @" + DateTime.Now);
            var now = DateTime.Now;

            var url = " https://accounts.google.com/o/oauth2/token";
            var req = WebRequest.Create(url);
            SetCurrentRequest(req);

            req.Method = "POST";
            req.ContentType = "application/x-www-form-urlencoded";
            var qs = "refresh_token=" + _refreshToken + "&" +
                    "client_id=3088305420.apps.googleusercontent.com&" +
                            "client_secret=NBY47cSr12KuRG9tYxWqIq1w&" +
                                    "grant_type=refresh_token";
            var bts = Encoding.ASCII.GetBytes(qs);
            req.ContentLength = bts.Length;

            AsyncCallback GetResponseCallback = ar => {
                HttpWebResponse rsp;
                try
                {
                    rsp = req.EndGetResponse(ar) as HttpWebResponse;
                }
                catch (Exception e)
                {
                    AsyncUtils.FireOnException(OnException, e);
                    return;
                }
                using (rsp)
                {
                    TextReader reader = new StreamReader(rsp.GetResponseStream());
                    var text = reader.ReadToEnd();
                    var result = JSON.Instance.ToObject<Dictionary<string, object>>(text);
                    _token = (string) result["access_token"];
                    var expiresIn = int.Parse("" + result["expires_in"]);
                    _expireTime = now.AddSeconds(expiresIn);
                }
                _settings["gtask"]["data2"].StringValue = CryptUtils.RijndaelEncrypt(_token, "gtask");
                var ts = _expireTime.ToString("yyyyMMddHHmmss");
                _settings["gtask"]["data3"].StringValue = CryptUtils.RijndaelEncrypt(ts, "gtask");
                callback();
            };

            AsyncCallback GetRequestCallback = ar => {
                using (var stream = req.EndGetRequestStream(ar))
                {
                    stream.Write(bts, 0, bts.Length);
                }
                req.BeginGetResponse(GetResponseCallback, req);
            };

            req.BeginGetRequestStream(GetRequestCallback, null);
        }

        public void Disconnect()
        {
            _refreshToken = _settings["gtask"]["data1"].StringValue = "";
            _token = _settings["gtask"]["data2"].StringValue = "";
        }

        public void AbortProcessing()
        {
            _aborting = true;
            SetCurrentRequest(null);
        }

        private void SetCurrentRequest(WebRequest request)
        {
            try
            {
                if (_request != null)
                {
                    _request.Abort();
                }
            }
            catch
            {
                // do nothing
            }
            _request = request;
        }
    }
}